/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package me.minetsh.imaging.crop.util;

import ohos.global.configuration.Configuration;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

public final class AssetManager implements AutoCloseable{


    public static final int ACCESS_UNKNOWN = 0;
    public static final int ACCESS_RANDOM = 1;
    public static final int ACCESS_STREAMING = 2;
    public static final int ACCESS_BUFFER = 3;

    private static final String TAG = "AssetManager";
    private static final boolean localLOGV = false || false;

    private static final boolean DEBUG_REFS = false;

    private static final Object sSync = new Object();
    static AssetManager sSystem = null;

    private final long[] mOffsets = new long[2];

    private long mObject;


    private int mNumRefs = 1;
    private boolean mOpen = true;
    private HashMap<Long, RuntimeException> mRefStacks;

    public AssetManager() {
        synchronized (this) {
            if (DEBUG_REFS) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
            init(false);
            ensureSystemAssets();
        }
    }

    private static void ensureSystemAssets() {
        synchronized (sSync) {
            if (sSystem == null) {
                AssetManager system = new AssetManager(true);
                sSystem = system;
            }
        }
    }


    private AssetManager(boolean isSystem) {
        if (DEBUG_REFS) {
            synchronized (this) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
        }
        init(true);
    }

    public static AssetManager getSystem() {
        ensureSystemAssets();
        return sSystem;
    }

    public void close() {
        synchronized(this) {
            if (mOpen) {
                mOpen = false;
                decRefsLocked(this.hashCode());
            }
        }
    }

    public final InputStream open(String fileName) throws IOException {
        return open(fileName, ACCESS_STREAMING);
    }

    public final InputStream open(String fileName, int accessMode)
            throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long asset = openAsset(fileName, accessMode);
            if (asset != 0) {
                AssetInputStream res = new AssetInputStream(asset);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset file: " + fileName);
    }

    public native final String[] list(String path)
            throws IOException;

    public final InputStream openNonAsset(String fileName) throws IOException {
        return openNonAsset(0, fileName, ACCESS_STREAMING);
    }

    public final InputStream openNonAsset(String fileName, int accessMode)
            throws IOException {
        return openNonAsset(0, fileName, accessMode);
    }

    public final InputStream openNonAsset(int cookie, String fileName)
            throws IOException {
        return openNonAsset(cookie, fileName, ACCESS_STREAMING);
    }

    public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
            throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long asset = openNonAssetNative(cookie, fileName, accessMode);
            if (asset != 0) {
                AssetInputStream res = new AssetInputStream(asset);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset absolute file: " + fileName);
    }


    void xmlBlockGone(int id) {
        synchronized (this) {
            decRefsLocked(id);
        }
    }

    final long createTheme() {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long res = newTheme();
            incRefsLocked(res);
            return res;
        }
    }

    final void releaseTheme(long theme) {
        synchronized (this) {
            deleteTheme(theme);
            decRefsLocked(theme);
        }
    }

    protected void finalize() throws Throwable {
        try {
            if (DEBUG_REFS && mNumRefs != 0) {
                if (mRefStacks != null) {
                    for (RuntimeException e : mRefStacks.values()) {
                    }
                }
            }
            destroy();
        } finally {
            super.finalize();
        }
    }

    public final class AssetInputStream extends InputStream {
        public final int getAssetInt() {
            throw new UnsupportedOperationException();
        }
        public final long getNativeAsset() {
            return mAsset;
        }
        private AssetInputStream(long asset)
        {
            mAsset = asset;
            mLength = getAssetLength(asset);
        }
        public final int read() throws IOException {
            return readAssetChar(mAsset);
        }
        public final boolean markSupported() {
            return true;
        }
        public final int available() throws IOException {
            long len = getAssetRemainingLength(mAsset);
            return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
        }
        public final void close() throws IOException {
            synchronized (AssetManager.this) {
                if (mAsset != 0) {
                    destroyAsset(mAsset);
                    mAsset = 0;
                    decRefsLocked(hashCode());
                }
            }
        }
        public final void mark(int readlimit) {
            mMarkPos = seekAsset(mAsset, 0, 0);
        }
        public final void reset() throws IOException {
            seekAsset(mAsset, mMarkPos, -1);
        }
        public final int read(byte[] b) throws IOException {
            return readAsset(mAsset, b, 0, b.length);
        }
        public final int read(byte[] b, int off, int len) throws IOException {
            return readAsset(mAsset, b, off, len);
        }
        public final long skip(long n) throws IOException {
            long pos = seekAsset(mAsset, 0, 0);
            if ((pos+n) > mLength) {
                n = mLength-pos;
            }
            if (n > 0) {
                seekAsset(mAsset, n, 0);
            }
            return n;
        }

        protected void finalize() throws Throwable
        {
            close();
        }

        private long mAsset;
        private long mLength;
        private long mMarkPos;
    }

    public final int addAssetPath(String path) {
        return  addAssetPathInternal(path, false);
    }

    public final int addAssetPathAsSharedLibrary(String path) {
        return addAssetPathInternal(path, true);
    }

    private final int addAssetPathInternal(String path, boolean appAsLib) {
        synchronized (this) {
            int res = addAssetPathNative(path, appAsLib);
            return res;
        }
    }

    private native final int addAssetPathNative(String path, boolean appAsLib);


    public final int addOverlayPath(String idmapPath) {
        synchronized (this) {
            int res = addOverlayPathNative(idmapPath);
            return res;
        }
    }

    public native final int addOverlayPathNative(String idmapPath);

    public final int[] addAssetPaths(String[] paths) {
        if (paths == null) {
            return null;
        }

        int[] cookies = new int[paths.length];
        for (int i = 0; i < paths.length; i++) {
            cookies[i] = addAssetPath(paths[i]);
        }

        return cookies;
    }

    public native final boolean isUpToDate();

    public native final String[] getLocales();

    public native final String[] getNonSystemLocales();

    public native final Configuration[] getSizeConfigurations();

    public native final void setConfiguration(int mcc, int mnc, String locale,
                                              int orientation, int touchscreen, int density, int keyboard,
                                              int keyboardHidden, int navigation, int screenWidth, int screenHeight,
                                              int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
                                              int screenLayout, int uiMode, int majorVersion);

    native final int getResourceIdentifier(String type,
                                                       String name,
                                                       String defPackage);

    native final String getResourceName(int resid);
    native final String getResourcePackageName(int resid);
    native final String getResourceTypeName(int resid);
    native final String getResourceEntryName(int resid);

    private native final long openAsset(String fileName, int accessMode);
    private native final long openNonAssetNative(int cookie, String fileName,
                                                 int accessMode);
    private native final void destroyAsset(long asset);
    private native final int readAssetChar(long asset);
    private native final int readAsset(long asset, byte[] b, int off, int len);
    private native final long seekAsset(long asset, long offset, int whence);
    private native final long getAssetLength(long asset);
    private native final long getAssetRemainingLength(long asset);

     static final int STYLE_NUM_ENTRIES = 6;
     static final int STYLE_TYPE = 0;
     static final int STYLE_DATA = 1;
     static final int STYLE_ASSET_COOKIE = 2;
     static final int STYLE_RESOURCE_ID = 3;

    static final int STYLE_CHANGING_CONFIGURATIONS = 4;

     static final int STYLE_DENSITY = 5;
     native static final boolean applyStyle(long theme,
                                                       int defStyleAttr, int defStyleRes, long xmlParser,
                                                       int[] inAttrs, int[] outValues, int[] outIndices);
     native static final boolean resolveAttrs(long theme,
                                                         int defStyleAttr, int defStyleRes, int[] inValues,
                                                         int[] inAttrs, int[] outValues, int[] outIndices);
     native final boolean retrieveAttributes(
            long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
     native final int getArraySize(int resource);
     native final int retrieveArray(int resource, int[] outValues);
    private native final int getStringBlockCount();
    private native final long getNativeStringBlock(int block);


    public native final String getCookieName(int cookie);


    public native static final int getGlobalAssetCount();


    public native static final String getAssetAllocations();


    public native static final int getGlobalAssetManagerCount();

    private native final long newTheme();
    private native final void deleteTheme(long theme);
     native static final void applyThemeStyle(long theme, int styleRes, boolean force);
     native static final void copyTheme(long dest, long source);
     native static final void clearTheme(long theme);
     native static final void dumpTheme(long theme, int priority, String tag, String prefix);

    private native final long openXmlAssetNative(int cookie, String fileName);

    private native final String[] getArrayStringResource(int arrayRes);
    private native final int[] getArrayStringInfo(int arrayRes);
     native final int[] getArrayIntResource(int arrayRes);
     native final int[] getStyleAttributes(int themeRes);

    private native final void init(boolean isSystem);
    private native final void destroy();

    private final void incRefsLocked(long id) {
        if (DEBUG_REFS) {
            if (mRefStacks == null) {
                mRefStacks = new HashMap<Long, RuntimeException>();
            }
            RuntimeException ex = new RuntimeException();
            ex.fillInStackTrace();
            mRefStacks.put(id, ex);
        }
        mNumRefs++;
    }

    private final void decRefsLocked(long id) {
        if (DEBUG_REFS && mRefStacks != null) {
            mRefStacks.remove(id);
        }
        mNumRefs--;
        if (mNumRefs == 0) {
            destroy();
        }
    }
}
